//== SAGITTARIUS ============================================================================

//== BEGINNING OF CODE ===============================================================

#include "sagittarius/core/SagittariusCore.h"
#include "sagittarius/parser/scanner.h"
#include "sagittarius/io/io.h"

//#include <stdio.h>
#include <string.h>
#include <malloc.h>

#define DFA_ACC_NONE 0xffffffff

void init_symbols();
void clear_symbols();
void dfa_init_ps(sm_c_parserState* ps, constLexBuf buf);
void resume_dfa(sm_c_parserState* ps);
void dfa_print_acc_id(uint32 ac);

//-- Token Stack --

void token_stack_init(token_stack* s) {
    // TODO: when a link empties, recenter its insertion points?
    s->f = s->b = NULL;
}

void token_stack_release(token_stack* s) {
    // the token stack seems to be getting too big
    //uint32 c=0;
    token_stack_link *l = s->f, *k;
    while(l) {
        k=l;
        l = l->nxt;
        release_token_link(k);
        // for diagnostics only
        //++c;
    }
    free(s);
    /*SG_WRITE(2, "Number of links in token stack: ");
    sg_writeUInt(2, c);
    SG_WRITE(2, "\n");*/
}

uint64 token_stack_depth(token_stack* s) {
    uint64 n=0;
    token_stack_link* l = s->f;
    for(; l!=NULL; l=l->nxt) {
        n += token_link_depth(l);
    }
    return n;
}

uint64 token_link_depth(token_stack_link* l) {
    AT(l->t >= l->h);
    return(l->t - l->h);
}

token_stack_link* make_token_link_e(size_t c) {
    // would be nice to do it with just one malloc
    token_stack_link* l = malloc(sizeof(token_stack_link));
    l->s = malloc(c*sizeof(sg_token_base));
    l->c = c;
    l->h = l->t = 0;
    l->nxt = l->prv = NULL;
    return l;
}

token_stack_link* make_token_link_b(size_t c) {
    // would be nice to do it with just one malloc
    token_stack_link* l = malloc(sizeof(token_stack_link));
    l->s = malloc(c*sizeof(sg_token_base));
    l->c = c;
    l->h = l->t = c;
    l->nxt = l->prv = NULL;
    return l;
}

void release_token_link(token_stack_link* l) {
    free(l->s);
    free(l);
}

int token_link_push_front(token_stack_link* l, sg_token_base t) {
    if(l->h > 0) {
        l->s[--l->h] = t;
        return 1;
    } else {
        // can't backup any further
        return 0;
    }
}

int token_link_push_back(token_stack_link* l, sg_token_base t) {
    if(l->t < l->c) {
        l->s[l->t++] = t;
        return 1;
    } else {
        // can't advance any further
        return 0;
    }
}

int token_link_can_pop_front(token_stack_link* l) {
    if(l->h == l->t)
        return 0;
    else
        return 1;
}

int token_link_can_pop_back(token_stack_link* l) {
    if(l->h == l->t)
        return 0;
    else
        return 1;
}

sg_token_base token_link_pop_front(token_stack_link* l) {
    AT(token_link_can_pop_front(l), "Can't pop front");
    return l->s[l->h++];
}

sg_token_base token_link_pop_back(token_stack_link* l) {
    AT(token_link_can_pop_back(l), "Can't pop back");
    return l->s[--l->t];
}

void token_stack_push_front(token_stack* s, sg_token_base t) {
    //AN(s->f, "No link");
    if(!s->f) {
        AZ(s->b, "Back must also be empty");
        s->f = make_token_link_b(4096);
        s->b = s->f;
    }
    if(!token_link_push_front(s->f, t)) {
        AZ(s->f->prv, "Already has previous link");
        s->f->prv = make_token_link_b(4096);
        // link newly created into chain
        s->f->prv->nxt = s->f;
        s->f = s->f->prv;
        AT(token_link_push_front(s->f, t), "Failed to push");
    }
}

void token_stack_push_back(token_stack* s, sg_token_base t) {
    //AN(s->b, "No link");
    if(!s->b) {
        AZ(s->f, "Front must also be empty");
        s->b = make_token_link_e(4096);
        s->f = s->b;
    }
    if(!token_link_push_back(s->b, t)) {
        AZ(s->b->nxt, "Already has next link");
        s->b->nxt = make_token_link_e(4096);
        // link newly created into chain
        s->b->nxt->prv = s->b;
        s->b = s->b->nxt;
        AT(token_link_push_back(s->b, t), "Failed to push");
    }
}

int token_stack_can_pop_front(token_stack* s) {
    return token_link_can_pop_front(s->f);
}

int token_stack_can_pop_back(token_stack* s) {
    return token_link_can_pop_back(s->b);
}

sg_token_base token_stack_pop_front(token_stack* s) {
    sg_token_base t;
    AT(token_stack_can_pop_front(s), "Can't pop front");
    t = token_link_pop_front(s->f);
    if(!token_link_depth(s->f)) {
        token_stack_link* l = s->f;
        // release if empty
        s->f = s->f->nxt;
        release_token_link(l);
        if(s->f == NULL || s->b == NULL)
            s->f = s->b = NULL; // empty stack
    }
    return t;
}

sg_token_base token_stack_pop_back(token_stack* s) {
    sg_token_base t;
    AT(token_stack_can_pop_back(s), "Can't pop back");
    t = token_link_pop_back(s->b);
    if(!token_link_depth(s->f)) {
        token_stack_link* l = s->f;
        // release if empty
        s->f = s->f->nxt;
        release_token_link(l);
        if(s->f == NULL || s->b == NULL)
            s->f = s->b = NULL; // empty stack
    }
    return t;
}

int token_stack_empty(token_stack* s) {
    int v = (token_stack_depth(s) == 0) ? 1 : 0;
    if(v)
        AZ(s->f || s->b, "Not really empty");
    else
        AN(s->f && s->b, "Not supposed to be NULL");
    
    return (token_stack_depth(s) == 0);
}

sg_token_base token_link_front(token_stack_link* l) {
    AT(token_link_depth(l) > 0, "Empty");
    return l->s[l->h];
}

sg_token_base token_stack_front(token_stack* s) {
    AT(!token_stack_empty(s), "Stack is empty");
    return token_link_front(s->f);
}

sg_token_base token_link_back(token_stack_link* l) {
    AT(token_link_depth(l) > 0, "Empty");
    AT(l->t > 0, "Underflow");
    return l->s[l->t-1];
}

sg_token_base token_stack_back(token_stack* s) {
    AT(!token_stack_empty(s), "Stack is empty");
    return token_link_back(s->b);
}

// -- Scanner --

/// initScanner
void initScanner(scanner_t* s, constLexBuf ubuf) {
    dfa_init_ps(&s->ps, ubuf);
    s->t = malloc(sizeof(token_stack));
    token_stack_init(s->t);
    //default off-side behavior: both
    s->tol_offside = 1;
    s->tol_dlim_blocks = 1;
    #if SAGITTARIUS_LXALPHA_USE_GMP
        init_symbols();
    #endif
    s->ind = 0;
    while(LX_CMPALPHA_EQ_SPC(*s->ps.w, ' ')) {
        ++s->ps.w;
        ++s->ind;
    }
    s->iind = s->ind;
    SG_WRITE(2, "Initial indentation level: ");
    sg_writeUInt(2, s->ind);
    SG_WRITE(2, "\n");
    // # lines
    s->ln = 1;
    s->suppress_newline = 0;
}

/// finalizeScanner
void finalizeScanner(scanner_t* s) {
    token_stack_release(s->t);
    #if SAGITTARIUS_LXALPHA_USE_GMP
        clear_symbols();
    #endif
}

sg_token_t makeToken(uint32 tval, uint64 ln) {
    sg_token_t t;
    t.tok = tval;
    t.pos = NULL;
    t.ln = ln;
    return t;
}

sg_token_t makeTokenPos(uint32 tval, constLexBuf pos) {
    sg_token_t t;
    t.tok = tval;
    t.pos = pos;
    return t;
}

/// scannerTok
sg_token_t scannerTok(scanner_t* s) {
    start_over:
    if(s->ps.ef)
        return makeToken(SCANNER_INVALID_TOK, s->ln);
    if(LX_CMPALPHA_EQ_SPC(*s->ps.w, '\0')) 
        //end of input
        return makeToken(SCANNER_EOI, s->ln);
    if(token_stack_empty(s->t))
        scannerAdvance(s);
    if(s->suppress_newline && token_stack_front(s->t).tok == TOKEN_nl) {
        scannerAdvance(s);
        goto start_over;
    }
    return token_stack_front(s->t);
}

/// scannerTok_ internal version
sg_token_t scannerTok_(scanner_t* s) {
    if(s->ps.ef)
        return makeToken(SCANNER_INVALID_TOK, s->ln);
    if(LX_CMPALPHA_EQ_SPC(*s->ps.w, '\0')) 
        //end of input
        return makeToken(SCANNER_EOI, s->ln);
    if(token_stack_empty(s->t))
        scannerAdvance(s);
    return token_stack_front(s->t);
}

/// scannerEOI
int scannerEOI(scanner_t* s) {
    if(LX_CMPALPHA_EQ_SPC(*s->ps.w, '\0')) 
        return 1;
    else
        return 0;
}

/// scannerEmit
void scannerEmit(scanner_t* s, uint32 tok) {
    sg_token_base t;
    t.tok = tok;
    token_stack_push_front(s->t, t);
}

/// scannerAdvance
void scannerAdvance(scanner_t* s) {
    if(!token_stack_empty(s->t)) {
        SG_WRITE(2, "  Token stack popped\n");
        token_stack_pop_front(s->t);
        if(!token_stack_empty(s->t))
            return;
    }
    while(1) {
        sg_token_t tok;
        s->ps.s = 0; //set to start state
        s->ps.ac = DFA_ACC_NONE; //scanner is responsible for resetting this
        s->ps.strt = s->ps.w; //scanner is responsible for resetting this
        if(LX_CMPALPHA_EQ_SPC(*s->ps.w, '\0'))
            break;
        resume_dfa(&s->ps);
        
        if(s->ps.ef) {
            printf("Parsing terminated with error\n");
            return;
        } else if(s->ps.ac == TOKEN_nl) {
            uint32 ind=0;
            //if(!s->tol_offside)
                //continue; //eat newline
            //if(!s->indent_block)
                //continue; //ignore indents when not in an indented block
            // The newline should never be delivered to the
            // parser. It's just used to execute the off-side rule.
            // I changed my mind: the newline should be delivered to the parser &
            // used when opening blocks & statement separators/terminators
            while(LX_CMPALPHA_EQ_SPC(*s->ps.w, ' ')) {
                ++s->ps.w;
                ++ind;
            }
            SG_WRITE(2, "Indentation level: ");
            sg_writeUInt(2, ind);
            SG_WRITE(2, "\n");
            // set on scanner
            s->iind = s->ind;
            s->ind = ind;
            // reset indent
            //s->ps.indent = ind;
            tok = makeToken(TOKEN_nl, s->ln);
            token_stack_push_back(s->t, tok);
            // increment line number
            ++s->ln;
            return;
        } else if(s->ps.ac == TOKEN_strsng || s->ps.ac == TOKEN_strdbl) {
            tok = makeToken(s->ps.ac, s->ln);
            scanString(s, &tok);
            token_stack_push_back(s->t, tok);
            return;
        } else if(s->ps.ac != DFA_ACC_NONE) {
            printf("  Token: ");
            fflush(stdout);
            dfa_print_acc_id(s->ps.ac);
            printf("\n");
            tok.tok = s->ps.ac;
            tok.pos = s->ps.strt;
            tok.l = (size_t)(s->ps.w - s->ps.strt);
            tok.ln = s->ln;
            // indent
            //s->ind = s->ps.indent;
            printf("  String: ");
            fflush(stdout);
            LX_WRITE_BUFL(1, tok.pos, tok.l);
            printf("\n");
            fflush(stdout);
            token_stack_push_back(s->t, tok);
            return;
        } if(LX_CMPALPHA_EQ_SPC(*s->ps.w, '\0')) {
            //end of input
            return;
        } else {
            //this shouldn't happen
            AN(0, "Scanner stopped but is in neither a valid nor error state.");
        }
        fflush(stdout);
    }
    //end of input
    fprintf(stderr, "End of input\n");
    fflush(stderr);
    return;
}

block_info scannerOpenBlock(scanner_t* s) {
    block_info b;
    if(scannerTok_(s).tok == TOKEN_nl) {
        /*SG_WRITE(2, "Scanner indent: ");
        sg_writeUInt(2, s->ind);
        SG_WRITE(2, "\n");*/
        b.t = BLOCK_DELIM_OFFSIDE;
        b.tabbase = s->iind;
        scannerAdvance(s);
        b.tabset = s->ind;
        SG_WRITE(2, "Opening offside block with base ");
        sg_writeUInt(2, b.tabbase);
        SG_WRITE(2, ", set ");
        sg_writeUInt(2, b.tabset);
        SG_WRITE(2, "\n");
    } else if(scannerTok_(s).tok == OPEN_BLOCK_SYMBOL) {
        b.t = BLOCK_DELIM_PHRASE;
        scannerAdvance(s); // make symbol transparent to parser
    } else {
        b.t = BLOCK_DELIM_INVALID;
    }
    return b;
}

int scannerCloseBlock(scanner_t* s, block_info b) {
    //scannerAdvance(s);
    if(b.t == BLOCK_DELIM_PHRASE) {
        if(scannerTok(s).tok != CLOSE_BLOCK_SYMBOL)
            return 1; // wrong token
        scannerAdvance(s);
    }
    return 0;
}

uint64 lexSymToInt(LXConstAlphaType a) {
    return LX_GET_FIXED_VALUE(a) - 0x30;
}

uint64 scannerIntFromTok(sg_token_t t) {
    constLexBuf p=t.pos;
    size_t n;
    uint64 x=0;
    uint64 i;
    AT(t.tok == TOKEN_integral, "Expected an integral");
    for(n=0;n<t.l;++n, ++p) {
        i = lexSymToInt(*p);
        // validating input is the responsibility of the caller
        //if(i > 10)
            // invalid
            //return 0;
        x = (x<<3) + (x<<1) + i;
    }
    return x;
}

int scannerShouldCloseBlock(scanner_t* s, block_info b) {
    if(b.t == BLOCK_DELIM_PHRASE) {
        if(scannerTok(s).tok == CLOSE_BLOCK_SYMBOL)
            return 1;
        else
            return 0;
    } else {
        // offside
        if(s->ind <= b.tabbase)
            return 1;
        else
            return 0;
    }
}

/// scannerSuppNL
int scannerSuppNL(scanner_t* s, int state) {
    int x = s->suppress_newline;
    s->suppress_newline = state;
    return x;
}

void scanString(scanner_t* s, sg_token_t* tok) {
    char ctok='\0';
    AT(tok->tok == TOKEN_strsng || tok->tok == TOKEN_strdbl, "Expected single or double quotation mark");
    
    if(tok->tok == TOKEN_strsng)
        ctok = '\'';
    else if(tok->tok == TOKEN_strdbl)
        ctok = '"';
    
    tok->pos = s->ps.w;
    tok->l = 0;
    
    while(!LX_CMPALPHA_EQ_SPC(*s->ps.w, ctok)) {
        ++s->ps.w;
        ++tok->l;
    }
    // eat trailing quote
    ++s->ps.w;
}

void dumpToken(sg_token_t tok) {
    if(tok.tok == SCANNER_INVALID_TOK)
        SG_WRITE(1, "INVALID TOKEN\n");
    if(tok.tok == SCANNER_EOI)
        SG_WRITE(1, "SCANNER_EOI\n");
    printf("  Token: ");
    fflush(stdout);
    dfa_print_acc_id(tok.tok);
    printf("\n");
    printf("  String: ");
    fflush(stdout);
    LX_WRITE_BUFL(1, tok.pos, tok.l);
    printf("\n");
    fflush(stdout);
}